"""
This module provides a function to analyze the trajectories of individual items (i.e. the sequence 
of actions pertaining to that item), find reentries, and discard invalid actions. When a reentry 
is found, a copy of the item is created (with the same id suffix by an asterisk).
The main function in this module is analyze_item_trajectories(), which returns new lists of items 
and actions.
"""
from utils.namespace import Namespace


def analyze_item_trajectories(items, actions, verbose=True):
    """Computes and validates the sequence of movements of each individual item. This function 
    removes invalid actions and creates item replicas with different ids (by appending '*') to 
    represent the re-entry of items into the warehouse."""
    # compute the sequence of actions for each item
    action_sequence = dict((item.id, []) for item in items)
    for action in actions:
        for item_id in action.items:
            action_sequence[item_id].append(action)
            
    # analyze item movement sequences, creating new items 
    # for reentries, and keeping track of invalid actions
    invalid_actions = set()
    remaining_items = list(items)
    reentry_items = list()
    while len(remaining_items) > 0:
        clone, invalid = analyze_item_trajectory(remaining_items.pop(), action_sequence, verbose)
        invalid_actions.update(invalid)
        if clone is not None:
            remaining_items.append(clone)
            reentry_items.append(clone)
            
    # add newly created items and remove invalid actions from the actions's action list
    items = items + reentry_items
    actions = [a for a in actions if a not in invalid_actions]
    if verbose:
        print "Created %d new items for reentries" % len(reentry_items)
        print "Removed %d invalid actions" % len(invalid_actions)
    return items, actions
    
    
def analyze_item_trajectory(item, action_sequence, verbose):
    """Analyzes an item's trajectory (the sequence of actions involving the item) and creates a 
    copy of the item when the item reenters the warehouse. This function also checks for 
    inconsistencies in the trajectory, such as reentries from stacks and moves from a location 
    other than the item's last known location. The function returns a 2-tuple containing the item's 
    copy (or None if no copy is created), and a list of invalid actions."""
    if verbose:
        print "Analyzing %s..." % item,
    var = Namespace(index=0, location=None, clone=None, error=None, invalid_actions=[])
    while var.index < len(action_sequence[item.id]):
        action   = action_sequence[item.id][var.index]
        crane_id = action.meta.crane
        place_id = action.place
        if action.is_pickup:
            analyze_item_pickup(item, action, action_sequence, var)
            if var.clone is not None or var.error is not None:
                break
                
        # verify that the item's location is coherent across actions
        assert (var.location == (place_id if action.is_pickup else crane_id) or 
                var.index == 0 and var.location is None)
        # update the item's location in the warehouse
        var.location = crane_id if action.is_pickup else (place_id if action.internal else None)
            
        # and show its path
        if verbose:
            if var.index == 0:
                print (place_id if action.is_pickup else crane_id),
            if var.location is None:
                print ("> %s (OUTSIDE)" % place_id),
            else:
                print ("> %s" % var.location),
        var.index += 1
        
    if verbose:
        if var.clone is not None:
            print "REENTRY as", var.clone,
        if var.error is None:
            print "OKAY"
        else:
            print "ERROR"
            print "Reason:", var.error
            print "Action:", var.invalid_actions[0]
    return var.clone, var.invalid_actions
    
    
def analyze_item_pickup(item, action, action_sequence, var):
    """This function analyzes a pickup action to check whether it is a reentry, and creates a copy 
    of the item if necessary (the clone is placed in 'var.clone'). If an error is detected, an 
    error string is assigned to 'var.error' and the actions remaining in the item's sequence are 
    added to 'var.invalid_actions'."""
    assert action.is_pickup
    item_sequence = action_sequence[item.id]
    
    def set_error(message):
        var.error = message
        var.invalid_actions.extend(item_sequence[var.index:])
        del item_sequence[var.index:]
        
    if var.location is None and var.index > 0:  # item is reentering the warehouse
        if action.internal:  # from a stack?!? something weird happened :o
            set_error("item mysteriously appeared in a stack after leaving the warehouse")
            return
        # this is a correct reentry (from an entrance/exit location): 
        # create new item to represent the reentry
        clone = item.deepcopy()
        clone.id = str(item.id) + "*"
        action_sequence[clone.id] = item_sequence[var.index:]
        del item_sequence[var.index:]
        # fix item id in actions in the sequence pertaining to the copied item
        for action in action_sequence[clone.id]:
            idx = action.items.index(item.id)
            action.items[idx] = clone.id
        # store the copied item in 'var.clone' since this function does not return
        var.clone = clone
        
    elif var.location is not None and var.location != action.place:
        set_error("item being picked up from a place mismatching its last known location")
        
        
        
